package com.jiao.syntax;

import com.jiao.syntax.entity.CselTemplate;
import com.jiao.syntax.entity.ExpressionTemplate;
import com.jiao.syntax.entity.Token;
import com.jiao.syntax.enums.TokenKind;
import com.jiao.syntax.exception.ExpressionException;
import com.jiao.syntax.exception.ParseException;
import com.sun.corba.se.spi.ior.ObjectKey;
import lombok.Getter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.jiao.syntax.enums.TokenKind.FIELD;
import static com.jiao.syntax.enums.TokenKind.FROM;

/**
 * 缓存sql解析模板
 * @Author: vincent.jiao
 * @Date: 2021/4/21
 */
public class CsplExpressionParser extends AbstractCsplExpressionParse {
    public static void main(String[] arsg){
        //TODO 测试分词表达式
//        String str = " select aeitem where itm_id = 1\n" +
//                "and itm_id not in (2,3)\n" +
//                "and itm_title = 'jiao' and itm_title in ( 'jiao1', 'jiao2', 'jiao3' )\n" +
//                "and itm_id between 1 and 5\n" +
//                "and itm_id > 5\n" +
//                "and itm_id < 5\n" +
//                "and itm_id <= 5\n" +
//                "and itm_id >= 5\n" +
//                "and itm_id != 5\n" +
//                "and (\n" +
//                "   itm_id = 1 or itm_id = 2\n" +
//                ") ";
//        String str = " select * from aeitem " +
//                "where itm_id = 1 " +
//                "and itm_title like 'jiao' " +
//                "and itm_title like #{jiao} " +
//                "or ( " +
//                "( itm_id in ( 'j', 'j' ) and itm_id in ( 'j', 'j' ) and itm_type = 'jiao') " +
//                " or " +
//                " (itm_id = 2 and itm_type = 'jiao2') " +
//                ") ";

        String str = " select * from aeitem " +
                "where itm_id = 1 " +
                "and itm_title like 'jiao' " +
                "and itm_title like #{jiao} " +
                "and itm_id = ? "+
                "and itm_id between ? and ? " +
                "and itm_id between #{num1} and #{num2} ";

        CsplExpressionParser parser = new CsplExpressionParser(str);
        System.out.println(parser);

    }

    private int pos = 0;
    private List<CselTemplate> whereCsqlTemplate = new LinkedList<>();
    private static final Map<String, ExpressionTemplate> expressionCache = new ConcurrentHashMap<>(2 << 10);

    public CsplExpressionParser(String expression) {
        super(expression);
        parseExpression(expression);
    }

    @Override
    public ExpressionTemplate parseExpression(String expressionString) throws ParseException {
        //TODO 缓存表达式，开发屏蔽，测试完开放
//        ExpressionTemplate expressionTemplate = expressionCache.get(expressionString);
//        if(expression != null){
//            return initInfo(expressionTemplate);
//        }

        process();
        return initInfo();
    }

    private void process() {
        super.tokenizer = new Tokenizer(expression);
        tokens = super.tokenizer.getTokens();
        processToken();
    }

    public List<CselTemplate> getCselTemplate () {
        return new LinkedList<>(whereCsqlTemplate);
    }

    private ExpressionTemplate initInfo() {
        this.expressionTemplate = new ExpressionTemplate();
        this.expressionTemplate.setCselTemplates(whereCsqlTemplate);
        this.expressionTemplate.setTableName(tableName);
        this.expressionTemplate.setSourceValue(expression);
        return expressionTemplate;
    }

    protected void processToken() {
        handlerIdentifier();
        parseSyntax();
    }

    @Override
    protected void parseSyntax() {
        checkSyntax();
        parseColunm();
        parseTableName();

        Map<Integer, TokenKind> relationIdentifierPos = null;   //关系标识符存放指针
        while (pos < tokens.size()){
            Token token = tokens.get(pos);

            switch (token.getKind()){
                case EQ:
                case GE:
                case GT:
                case LE:
                case LT:
                case NE:
                case IN:
                case NOTIN:
                case LIKE:
                    handlerOperator();
                    break;
                case BETWEEN:
                    handlerBetween();
                    break;
                case OR:
                case AND:
                    if(relationIdentifierPos == null){
                        relationIdentifierPos = new HashMap<>();
                    }

                    relationIdentifierPos.put(whereCsqlTemplate.size(), token.getKind());
                    pos++;
                    break;
                case LPAREN:
                case RPAREN:
                    handlerGroup();
                    pos++;
                    break;
                default:
                    pos++;
            }
        }

        //处理关系标识符指针
        if(relationIdentifierPos != null){
            for (Map.Entry<Integer, TokenKind> entry  : relationIdentifierPos.entrySet()){

                if(entry.getKey() < whereCsqlTemplate.size()){
                    whereCsqlTemplate.get(entry.getKey()).setRelationIdentifier(entry.getValue());
                }
            }
        }
    }

    /**
     * 解析出来所有列.
     */
    private void parseColunm() {
        for (; pos < tokens.size(); pos++){
            Token token = tokens.get(pos);

            if(token.getKind().equals(FROM)) {
                break;
            }

            if (token.getKind().equals(FIELD)) {
                colunmNameList.add(token.stringValue());
            }
        }
    }

    /**
     * 解析出表名.
     */
    private void parseTableName() {
        for (int i = 0; i < tokens.size(); i++) {
            Token token = tokens.get(i);

            if(FROM.equals(token.getKind())) {
                tableName = tokens.get(i + 1).stringValue();
                break;
            }
        }

    }

    private void handlerGroup(){
        Token token = tokens.get(pos);
        boolean isStartGroup = TokenKind.LPAREN.equals(token.getKind());
        CselTemplate template = new CselTemplate();
        template.setGroup(true);
        addCselTemplate(template);

        if(isStartGroup){
            template.setStartGroup(true);
        }else{
            template.setEndGroup(true);
        }
    }

    /**
     * 检查语法.
     */
    private void checkSyntax(){
        TokenKind[] syntaxTrunk = {TokenKind.SELECT, TokenKind.FROM};       //, TokenKind.WHERE

        //检查语法主干结构是否符合
        int count = 0;
        for (int i = 0, j = 0; i < syntaxTrunk.length; i++) {
            for (; j < tokens.size(); j++) {
                if (syntaxTrunk[i].equals(tokens.get(j).getKind())) {
                    count++;
                    break;
                }
            }
        }

        if (syntaxTrunk.length != count) {
            throwException("语法错误，表达式格式例如)：select [colunmName] from [tableName] where [colunmName] = 1", -1, -1);
        }
    }

    /**
     * 处理 between
     */
    private void handlerBetween(){
        CselTemplate cselTemplate = new CselTemplate();
        Token token = getField(pos - 1);
        cselTemplate.setField(token.getDataStr());

        cselTemplate.setOperationIdentifier(TokenKind.BETWEEN);

        List values = new ArrayList(2);
        token = getValue(++pos);
        values.add(token.getKind().equals(TokenKind.PRECOMPILE) ? "?" : token.getDataStr());
        TokenKind valueType = token.getKind();

        if(!TokenKind.AND.equals(tokens.get(++pos).getKind())){
            throwException("between 出现语法错误，索引：", token.getStartPos(), token.getEndPos());
        }

        token = getValue(++pos);
        values.add(token.getKind().equals(TokenKind.PRECOMPILE) ? "?" : token.getDataStr());

        if(!valueType.equals(token.getKind())){
            throwException("数据类型不一致：", token.getStartPos(), token.getEndPos());
        }

        cselTemplate.setValues(values);
        cselTemplate.setValueType(valueType);

        addCselTemplate(cselTemplate);
    }

    private Token getField(int pos){
        Token token = tokens.get(pos);

        if(!Token.isField(token)){
            throwException("解析字段出现语法错误，索引：", token.getStartPos(), token.getEndPos());
        }

        return token;
    }

    private Token getValue(int pos){
        Token token = tokens.get(pos);

        if(!Token.isValueType(token)){
            throwException("解析值出现语法错误，索引：", token.getStartPos(), token.getEndPos());
        }

        return token;
    }

    private CselTemplate getLastCselTemplate(){
        CselTemplate cselTemplate = null;
        if( whereCsqlTemplate.size() > 0){
            cselTemplate = whereCsqlTemplate.get(whereCsqlTemplate.size() - 1);
        }

        return cselTemplate;
    }

    private void handlerOperator(){
        CselTemplate cselTemplate = new CselTemplate();
        Token token = getField(pos - 1);

        cselTemplate.setField(token.getDataStr());

        token = tokens.get(pos);
        if(!Token.isOperation(token)){
            throwException("出现语法错误，位置：", token.getStartPos(), token.getEndPos());
        }
        cselTemplate.setOperationIdentifier(tokens.get(pos).getKind());

        List list = handlerValue(cselTemplate);
        cselTemplate.setValues(list);
        addCselTemplate(cselTemplate);
    }

    private void addCselTemplate(CselTemplate cselTemplate){
        whereCsqlTemplate.add(cselTemplate);
    }

    private List handlerValue(CselTemplate cselTemplate){
        List list = null;

        Token token = tokens.get(++pos);
        TokenKind valueType = null;

        if(TokenKind.LPAREN.equals(token.getKind())){
            int parseValueCount = 0;
            list = new ArrayList(16);
            while (Token.isValueType(token = tokens.get(++pos))){
                ++parseValueCount;
                list.add(token.getDataStr());
                valueType = token.getKind();

                token = tokens.get(++pos);
                if(TokenKind.RPAREN.equals(tokens.get(pos).getKind())){
                    break;
                } else if(!TokenKind.COMMA.equals(token.getKind())){
                    throwException(null, token.getStartPos(), token.getEndPos());
                }
            }

            if(parseValueCount == 0 ){
                throwException(null, token.getStartPos(), token.getEndPos());
            }

        } else if (Token.isValueType(token)){
            list = new ArrayList(1);
            list.add(token.getDataStr());
            valueType = token.getKind();

        } else if (Token.isPrecompile(token)){
            //占位符
            list = new ArrayList(1);
            list.add(token.getDataStr());
            valueType = token.getKind();

        } else {
            throwException(null, token.getStartPos(), token.getEndPos());
        }

        //下推指针
        ++pos;
        cselTemplate.setValueType(valueType);
        return list;
    }


    private void throwException(String msg, int start, int end){
        if(msg == null || msg.isEmpty()){
            msg = "表达式出现错误，起始索引：";
        }

        throw new ExpressionException(msg, start, end);
    }

    /**
     * 处理标识符（忽略大小写）.
     */
    private void handlerIdentifier(){
        for (int i = 0, size = tokens.size(); i < size; i++) {
            Token token = tokens.get(i);
            TokenKind tokenKind = token.getKind();

            if(TokenKind.IDENTIFIER.equals(tokenKind)){
                String enumValue = token.getDataStr();
                TokenKind returnTokenKind = null;

                if(TokenKind.BETWEEN.getValue().equalsIgnoreCase(enumValue)){
                    returnTokenKind = TokenKind.BETWEEN;
                } else if(TokenKind.IN.getValue().equalsIgnoreCase(enumValue)){
                    returnTokenKind = TokenKind.IN;
                } else if(TokenKind.OR.getValue().equalsIgnoreCase(enumValue)){
                    returnTokenKind = TokenKind.OR;
                } else if(TokenKind.AND.getValue().equalsIgnoreCase(enumValue)){
                    returnTokenKind = TokenKind.AND;
                } else if(TokenKind.NOTIN.getValue().equalsIgnoreCase(enumValue)){
                    returnTokenKind = TokenKind.NOTIN;
                    Token inToken = tokens.get(i + 1);

                    //合并
                    if(TokenKind.IN.getValue().equalsIgnoreCase(inToken.getDataStr())){
                        Token newToken = new Token(TokenKind.NOTIN, token.getDataStr()+inToken.getDataStr(),
                                token.getStartPos(), inToken.getEndPos());

                        tokens.set(i, newToken);
                        tokens.remove(inToken);
                        --size;
                    }

                } else {
                    returnTokenKind = TokenKind.FIELD;
                }

                token.setKind(returnTokenKind);
            }
        }
    }
}
